<?php
namespace app\http\middleware;

use think\Request;
use think\Response;
use think\facade\Log;
use think\Db;
use app\service\ApiKeyService;

/**
 * API认证中间件
 */
class ApiAuth
{
    /**
     * 处理请求
     *
     * @param Request $request
     * @param \Closure $next
     * @return Response
     */
    public function handle(Request $request, \Closure $next)
    {
        $startTime = microtime(true);
        
        try {
            // 1. 提取认证信息
            $authResult = $this->extractAuthInfo($request);
            if (!$authResult['success']) {
                return $this->errorResponse($authResult['message'], 401);
            }
            
            $apiKey = $authResult['api_key'];
            $timestamp = $authResult['timestamp'];
            $signature = $authResult['signature'];
            
            // 2. 验证API Key
            $keyInfo = ApiKeyService::validateApiKey($apiKey);
            if (!$keyInfo) {
                $this->logApiCall($apiKey, $request, 401, 'Invalid API Key', $startTime);
                return $this->errorResponse('Invalid API Key', 401);
            }
            
            // 3. 验证时间戳（防重放攻击）
            if (!ApiKeyService::verifyTimestamp($timestamp)) {
                $this->logApiCall($apiKey, $request, 401, 'Invalid timestamp', $startTime);
                return $this->errorResponse('Invalid timestamp', 401);
            }
            
            // 4. 验证签名
            $method = $request->method();
            $path = $request->pathinfo();
            $body = $request->getContent();

            // 签名验证已通过，移除调试信息
            if (!ApiKeyService::verifySignature($method, $path, $timestamp, $body, $signature, $keyInfo['secret_key'])) {
                $this->logApiCall($apiKey, $request, 401, 'Invalid signature', $startTime);
                return $this->errorResponse('Invalid signature', 401);
            }
            
            // 5. 验证IP白名单（暂时跳过）
            $clientIp = $this->getClientIp($request);
            $allowedIps = json_decode($keyInfo['ip_whitelist'] ?? '[]', true);

            // 暂时注释掉IP验证，方便测试
            /*
            if (!ApiKeyService::verifyIpWhitelist($clientIp, $allowedIps)) {
                $this->logApiCall($apiKey, $request, 403, 'IP not in whitelist', $startTime);
                return $this->errorResponse('Access denied: IP not in whitelist', 403);
            }
            */

            Log::info("跳过IP验证: 客户端IP={$clientIp}, 白名单=" . json_encode($allowedIps));
            
            // 6. 验证权限（可选）
            $permissions = json_decode($keyInfo['permissions'] ?? '[]', true);
            if (!$this->checkPermissions($request, $permissions)) {
                $this->logApiCall($apiKey, $request, 403, 'Insufficient permissions', $startTime);
                return $this->errorResponse('Insufficient permissions', 403);
            }
            
            // 7. 将API Key信息添加到请求中
            $request->apiKeyInfo = $keyInfo;
            
            // 8. 继续处理请求
            $response = $next($request);
            
            // 9. 记录成功的API调用
            $this->logApiCall($apiKey, $request, 200, 'Success', $startTime);
            
            return $response;
            
        } catch (\Throwable $e) {
            Log::error("API认证中间件异常: " . $e->getMessage());
            $this->logApiCall($apiKey ?? 'unknown', $request, 500, 'Internal error', $startTime);
            return $this->errorResponse('Internal server error', 500);
        }
    }
    
    /**
     * 提取认证信息
     *
     * @param Request $request
     * @return array
     */
    private function extractAuthInfo(Request $request): array
    {
        // 从请求头获取认证信息
        $authorization = $request->header('Authorization');
        $timestamp = $request->header('Timestamp');
        $signature = $request->header('Signature');
        
        // 验证Authorization格式: "ApiKey your-api-key"
        if (!$authorization || !preg_match('/^ApiKey\s+(.+)$/', $authorization, $matches)) {
            return ['success' => false, 'message' => 'Missing or invalid Authorization header'];
        }
        
        $apiKey = trim($matches[1]);
        
        if (empty($timestamp)) {
            return ['success' => false, 'message' => 'Missing Timestamp header'];
        }
        
        if (empty($signature)) {
            return ['success' => false, 'message' => 'Missing Signature header'];
        }
        
        return [
            'success' => true,
            'api_key' => $apiKey,
            'timestamp' => $timestamp,
            'signature' => $signature
        ];
    }
    
    /**
     * 检查权限
     *
     * @param Request $request
     * @param array $permissions
     * @return bool
     */
    private function checkPermissions(Request $request, array $permissions): bool
    {
        // 如果没有设置权限，则允许所有操作
        if (empty($permissions)) {
            return true;
        }
        
        $path = $request->pathinfo();
        
        // 根据路径判断所需权限
        $requiredPermission = $this->getRequiredPermission($path);
        
        if (!$requiredPermission) {
            return true; // 不需要特殊权限
        }
        
        return in_array($requiredPermission, $permissions);
    }
    
    /**
     * 根据路径获取所需权限
     *
     * @param string $path
     * @return string|null
     */
    private function getRequiredPermission(string $path): ?string
    {
        $permissionMap = [
            'api/device/command' => 'device:command',
            'api/device/query' => 'device:query',
            'api/system/admin' => 'system:admin',
        ];
        
        foreach ($permissionMap as $pathPattern => $permission) {
            if (strpos($path, $pathPattern) === 0) {
                return $permission;
            }
        }
        
        return null;
    }
    
    /**
     * 获取客户端IP
     *
     * @param Request $request
     * @return string
     */
    private function getClientIp(Request $request): string
    {
        // 优先从代理头获取真实IP
        $headers = [
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'HTTP_CLIENT_IP',
            'REMOTE_ADDR'
        ];
        
        foreach ($headers as $header) {
            $ip = $request->server($header);
            if (!empty($ip) && $ip !== 'unknown') {
                // 如果是多个IP，取第一个
                $ips = explode(',', $ip);
                return trim($ips[0]);
            }
        }
        
        return $request->ip();
    }
    
    /**
     * 记录API调用日志
     *
     * @param string $apiKey
     * @param Request $request
     * @param int $responseCode
     * @param string $responseMessage
     * @param float $startTime
     */
    private function logApiCall(string $apiKey, Request $request, int $responseCode, string $responseMessage, float $startTime): void
    {
        try {
            $executionTime = round((microtime(true) - $startTime) * 1000); // 毫秒
            
            $logData = [
                'api_key' => $apiKey,
                'request_id' => $request->header('X-Request-ID') ?: uniqid('req_'),
                'method' => $request->method(),
                'path' => $request->pathinfo(),
                'client_ip' => $this->getClientIp($request),
                'user_agent' => $request->header('User-Agent'),
                'request_body' => $request->getContent(),
                'response_code' => $responseCode,
                'response_message' => $responseMessage,
                'execution_time' => $executionTime,
                'created_at' => date('Y-m-d H:i:s')
            ];
            
            // 异步记录日志，避免影响响应性能
            Db::connect('db_config2')->name('api_call_logs')->insert($logData);
            
        } catch (\Throwable $e) {
            Log::error("记录API调用日志失败: " . $e->getMessage());
        }
    }
    
    /**
     * 返回错误响应
     *
     * @param string $message
     * @param int $code
     * @return Response
     */
    private function errorResponse(string $message, int $code): Response
    {
        $data = [
            'code' => 0,
            'message' => $message,
            'timestamp' => date('Y-m-d H:i:s')
        ];
        
        return Response::create($data, 'json', $code);
    }
}
